﻿using System;
using System.Net;
using System.Net.Sockets;
using Transformable_Engine_v2.Engine.Network.Interfaces;
using Transformable_Engine_v2.Engine.Network.Object;

namespace Transformable_Engine_v2.Engine.Network
{
    public class UdpNetworkClient : BaseNetworkClient, INetworkClient
    {
        private readonly ClientType _type;

        private UdpClient _listenClient;

        public UdpNetworkClient(ClientType clientType)
        {
            _type = clientType;
        }

        public void Receive(IPAddress address,
                            int port)
        {
            if(address == null)
                address = IPAddress.Any;

            switch(_type) 
            {
                case ClientType.Sync:
                    ReceiveSyncMessage(address, port);
                    break;
                case ClientType.Async:
                    ReceiveAsyncMessage(address, port);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        void ReceiveAsyncMessage(IPAddress listenAddress, int listenPort)
        {
            IPEndPoint ip = new IPEndPoint(listenAddress, listenPort);
            _listenClient = new UdpClient(ip);

            _listenClient.BeginReceive(ReceiveCallback, null);
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 0);
            byte[] data = _listenClient.EndReceive(ar, ref ipEndPoint);
            RaiseOnReceiveEvent(ipEndPoint, data);

            if(_isRunning)
                _listenClient.BeginReceive(ReceiveCallback, null);
        }

        void ReceiveSyncMessage(IPAddress listenAddress, int listenPort)
        {
            
            IPEndPoint ip = new IPEndPoint(listenAddress, listenPort);
            UdpClient client = new UdpClient(ip);

            while(_isRunning) {
                RaiseOnStartListenEvent();

                IPEndPoint clientInformation = new IPEndPoint(IPAddress.Any, 0);
                byte[] data = client.Receive(ref clientInformation);

                RaiseOnReceiveEvent(clientInformation, data);
            }
            
        }

        public void Send(string hostName,
                                int port,
                                byte[] data)
        {
            switch(_type) 
            {
                case ClientType.Sync:
                    SendSyncMessage(hostName, port, data);
                    break;
                case ClientType.Async:
                    SendAsyncMessage(hostName, port, data);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void Send(IPAddress address, int port, INetworkSerializable message)
        {
            var bytes = Serialization.Serialize(new NetworkSerializableObject(message));
            Send(address, port, bytes);
        }

        public void Send(IPAddress[] address, int port, INetworkSerializable message)
        {
            var bytes = Serialization.Serialize(new NetworkSerializableObject(message));
            foreach (var ip in address)
            {
                Send(ip, port, bytes);
            }
        }

        public void Send(IPAddress address,
                                int port,
                                byte[] data)
        {
            if(address == null)
                address = IPAddress.Any;

            switch(_type) 
            {
                case ClientType.Sync:
                    SendSyncMessage(address, port, data);
                    break;
                case ClientType.Async:
                    SendAsyncMessage(address, port, data);
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        void SendAsyncMessage(IPAddress address,
                                int port,
                                byte[] data)
        {
            UdpClient client = new UdpClient(address.AddressFamily);
            client.Connect(address, port);

            RaiseOnSendEvent(address.ToString(), port.ToString(), data);

            client.BeginSend(data, data.Length, SendCallback, null);
        }

        private void SendCallback(IAsyncResult ar)
        {
            //The message is successfully sent
            //Nothing more to do
        }

        void SendAsyncMessage(string hostName,
                                int port,
                                byte[] data)
        {
            UdpClient client = new UdpClient();
            client.Connect(hostName, port);

            RaiseOnSendEvent(hostName, port.ToString(), data);
            client.BeginSend(data, data.Length, SendCallback, null);
        }

        void SendSyncMessage(IPAddress address,
                                int port,
                                byte[] data)
        {
            UdpClient client = new UdpClient(address.AddressFamily);
            client.Connect(address, port);

            RaiseOnSendEvent(address.ToString(), port.ToString(), data);
            client.Send(data, data.Length);
        }

        void SendSyncMessage(string hostName,
                                int port,
                                byte[] data)
        {
            UdpClient client = new UdpClient();
            client.Connect(hostName, port);

            RaiseOnSendEvent(hostName, port.ToString(), data);
            client.Send(data, data.Length);
        }
    }
}